// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

// mcpack2pb - Make protobuf be front-end of mcpack/compack

// Date: Mon Oct 19 17:17:36 CST 2015

#ifndef MCPACK2PB_MCPACK_PARSER_H
#define MCPACK2PB_MCPACK_PARSER_H

#include <limits>  // std::numeric_limits
#include <google/protobuf/io/zero_copy_stream.h>
#include <krpc/utility/logging.h>
#include <krpc/utility/strings/string_piece.h>
#include <krpc/mcpack2pb/field_type.h>

// CAUTION: Methods in this header is not intended to be public to users of
// krpc, and subject to change at any future time.

namespace mcpack2pb {
    // Read bytes from ZeroCopyInputStream
    class InputStream {
    public:
        InputStream(google::protobuf::io::ZeroCopyInputStream *stream)
            : _good(true)
              , _size(0)
              , _data(NULL)
              , _zc_stream(stream)
              , _popped_bytes(0) {
        }

        ~InputStream() {
        }

        // Pop at-most n bytes from front side.
        // Returns bytes popped.
        size_t popn(size_t n);

        // Cut off at-most n bytes from front side and copy to `out'.
        // Returns bytes cut.
        size_t cutn(void *out, size_t n);

        template<typename T>
        size_t cut_packed_pod(T *packed_pod);

        template<typename T>
        T cut_packed_pod();

        // Cut off at-most n bytes from front side. If the data is stored in
        // continuous memory, return the reference directly, otherwise copy
        // the data into `aux' and return reference of `aux'.
        // Returns a StringPiece referencing the cut-off data.
        kutil::StringPiece ref_cut(std::string *aux, size_t n);

        // Peek at the first character. If the stream is empty, 0 is returned.
        uint8_t peek1();

        // Returns bytes popped and cut since creation of this stream.
        size_t popped_bytes() const { return _popped_bytes; }

        // Returns false if error occurred in other consuming functions.
        bool good() const { return _good; }

        // If the error prevents parsing from going on, call this method.
        // This method is also called in other functions in this class.
        void set_bad() { _good = false; }

    private:
        bool _good;
        int _size;
        const void *_data;
        google::protobuf::io::ZeroCopyInputStream *_zc_stream;
        size_t _popped_bytes;
    };

    class ObjectIterator;
    class ArrayIterator;
    class ISOArrayIterator;

    // Represent a piece of unparsed(and unread) data of InputStream.
    struct UnparsedValue {
        UnparsedValue()
            : _type(FIELD_UNKNOWN), _stream(NULL), _size(0) {
        }

        UnparsedValue(FieldType type, InputStream *stream, size_t size)
            : _type(type), _stream(stream), _size(size) {
        }

        void set(FieldType type, InputStream *stream, size_t size) {
            _type = type;
            _stream = stream;
            _size = size;
        }

        FieldType type() const { return _type; }
        InputStream *stream() { return _stream; }
        const InputStream *stream() const { return _stream; }
        size_t size() const { return _size; }

        // Convert to concrete value. These functions can only be called once!
        ObjectIterator as_object();

        ArrayIterator as_array();

        ISOArrayIterator as_iso_array();

        // `var' in following methods should be a description of the field being
        // parsed, for better error reporting. It has nothing to do with the result.
        int64_t as_int64(const char *var);

        uint64_t as_uint64(const char *var);

        int32_t as_int32(const char *var);

        uint32_t as_uint32(const char *var);

        bool as_bool(const char *var);

        float as_float(const char *var);

        double as_double(const char *var);

        void as_string(std::string *out, const char *var);

        std::string as_string(const char *var);

        void as_binary(std::string *out, const char *var);

        std::string as_binary(const char *var);

    private:
        friend class ObjectIterator;
        friend class ArrayIterator;
        void set_end() { _type = FIELD_UNKNOWN; }

        FieldType _type;
        InputStream *_stream;
        size_t _size;
    };

    std::ostream &operator<<(std::ostream &os, const UnparsedValue &value);

    // Remove the header of the wrapping object.
    // Returns the value size.
    size_t unbox(InputStream *stream);

    // Iterator all fields in an object which should be created like this:
    //   ObjectIterator it(unparsed_value);
    //   for (; it != NULL; ++it) {
    //     std::cout << "name=" << it->name << " value=" << it->value << std::endl;
    //   }
    class ObjectIterator {
    public:
        struct Field {
            kutil::StringPiece name;
            UnparsedValue value;
        };

        // Parse `n' bytes from `stream' as fields of an object.
        ObjectIterator(InputStream *stream, size_t n) { init(stream, n); }
        explicit ObjectIterator(UnparsedValue &value) { init(value.stream(), value.size()); }

        ~ObjectIterator() {
        }

        Field *operator->() { return &_current_field; }
        Field &operator*() { return _current_field; }

        void operator++();

        operator void *() const { return (void *) _current_field.value.type(); }

        // Number of fields in the object.
        uint32_t field_count() const { return _field_count; }

    private:
        void init(InputStream *stream, size_t n);

        void set_bad() {
            set_end();
            _stream->set_bad();
        }

        void set_end() { _current_field.value._type = FIELD_UNKNOWN; }
        size_t left_size() const { return _expected_popped_end - _expected_popped_bytes; }

        Field _current_field;
        uint32_t _field_count;
        std::string _name_backup_string;
        InputStream *_stream;
        size_t _expected_popped_bytes;
        size_t _expected_popped_end;
    };

    // Iterator all items in a (mcpack) array which should be created like this:
    //   ArrayIterator it(unparsed_value);
    //   for (; it != NULL; ++it) {
    //     std::cout << " item=" << *it << std::endl;
    //   }
    class ArrayIterator {
    public:
        typedef UnparsedValue Field;

        ArrayIterator(InputStream *stream, size_t size) { init(stream, size); }
        explicit ArrayIterator(UnparsedValue &value) { init(value.stream(), value.size()); }

        ~ArrayIterator() {
        }

        Field *operator->() { return &_current_field; }
        Field &operator*() { return _current_field; }

        void operator++();

        operator void *() const { return (void *) _current_field.type(); }

        // Number of items in the array.
        uint32_t item_count() const { return _item_count; }

    private:
        void init(InputStream *stream, size_t n);

        void set_bad() {
            set_end();
            _stream->set_bad();
        }

        void set_end() { _current_field._type = FIELD_UNKNOWN; }
        size_t left_size() const { return _expected_popped_end - _expected_popped_bytes; }

        Field _current_field;
        uint32_t _item_count;
        InputStream *_stream;
        size_t _expected_popped_bytes;
        size_t _expected_popped_end;
    };

    // Iterator all items in an isomorphic array which should be created like this:
    //   ISOArrayIterator it(unparsed_value);
    class ISOArrayIterator {
    public:
        ISOArrayIterator(InputStream *stream, size_t size) { init(stream, size); }
        explicit ISOArrayIterator(UnparsedValue &value) { init(value.stream(), value.size()); }

        ~ISOArrayIterator() {
        }

        void operator++();

        operator void *() const { return (void *) (uintptr_t) _item_type; }

        template<typename T>
        T as_integer() const;

        template<typename T>
        T as_fp() const;

        int64_t as_int64() const { return as_integer<int64_t>(); }
        uint64_t as_uint64() const { return as_integer<uint64_t>(); }
        int32_t as_int32() const { return as_integer<int32_t>(); }
        uint32_t as_uint32() const { return as_integer<uint32_t>(); }
        bool as_bool() const { return as_integer<bool>(); }
        float as_float() const { return as_fp<float>(); }
        double as_double() const { return as_fp<double>(); }

        PrimitiveFieldType item_type() const { return _item_type; }

        // Number of items in the array.
        uint32_t item_count() const { return _item_count; }

    private:
        void init(InputStream *stream, size_t n);

        void set_bad() {
            set_end();
            _stream->set_bad();
        }

        void set_end() { _item_type = (PrimitiveFieldType) 0; }

        int _buf_index;
        int _buf_count;
        InputStream *_stream;
        PrimitiveFieldType _item_type;
        uint32_t _item_size;
        uint32_t _item_count;
        uint32_t _left_item_count;
        char _item_buf[1024];
    };
} // namespace mcpack2pb

#include <krpc/mcpack2pb/parser-inl.h>

#endif  // MCPACK2PB_MCPACK_PARSER_H
